{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center>\n",
    "    <p style=\"text-align:center\">\n",
    "        <img alt=\"phoenix logo\" src=\"https://raw.githubusercontent.com/Arize-ai/phoenix-assets/9e6101d95936f4bd4d390efc9ce646dc6937fb2d/images/socal/github-large-banner-phoenix.jpg\" width=\"1000\"/>\n",
    "        <br>\n",
    "        <br>\n",
    "        <a href=\"https://arize.com/docs/phoenix/\">Docs</a>\n",
    "        |\n",
    "        <a href=\"https://github.com/Arize-ai/phoenix\">GitHub</a>\n",
    "        |\n",
    "        <a href=\"https://arize-ai.slack.com/join/shared_invite/zt-2w57bhem8-hq24MB6u7yE_ZF_ilOYSBw#/shared-invite/email\">Community</a>\n",
    "    </p>\n",
    "</center>\n",
    "<h1 align=\"center\">Phoenix Prompts + OpenAI + Anthropic JavaScript SDK Tutorial</h1>\n",
    "\n",
    "Let's see how to get started with using the Phoenix JavaScript SDK to pull prompts from an\n",
    "instance of Phoenix, and then invoke those prompts using multiple LLM SDKs, all in a Deno Jupyter notebook.\n",
    "\n",
    "> Note: that this example requires an OpenAI API key, an Anthropic API key, and assumes you are running the Phoenix server on localhost:6006."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import * as PhoenixClient from \"npm:@arizeai/phoenix-client\"\n",
    "import OpenAI from \"npm:openai\"\n",
    "import { Anthropic } from \"npm:@anthropic-ai/sdk\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's get started by creating a Phoenix client. This is technically optional, as the Phoenix Prompt helper methods will create a client if not provided.\n",
    "\n",
    "However, we will create a client here to show how to configure the client with your Phoenix instance."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "const px = PhoenixClient.createClient({\n",
    "  options: {\n",
    "    baseUrl: \"http://localhost:6006\",\n",
    "    // Uncomment this if you are using a Phoenix instance that requires an API key\n",
    "    // headers: {\n",
    "    //   Authorization: \"bearer xxxxxx\",\n",
    "    // }\n",
    "  }\n",
    "})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Next, let's setup the OpenAI client. This will prompt you for an OpenAI API key."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "const apiKey = prompt(\"Enter your OpenAI API key:\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "const openai = new OpenAI({ apiKey: apiKey });"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We will also setup the Anthropic client, which will prompt you for an Anthropic API key."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "const anthropicApiKey = prompt(\"Enter your Anthropic API key:\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "const anthropic = new Anthropic({ apiKey: anthropicApiKey });"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now let's build a prompt in Phoenix!\n",
    "\n",
    "We will create a prompt called `question-searcher` that asks a question and then returns the answer.\n",
    "\n",
    "Let's set up the prompt to use `OpenAI` as the LLM provider, and lets also add a \"variable\" to one of the prompt messages, named `query`.\n",
    "\n",
    "This will allow us to pass in a question to the prompt when we invoke it.\n",
    "\n",
    "Finally, lets add a tool called `search` to the prompt. This tool will allow the LLM to request a search for a query it receives.\n",
    "\n",
    "```json\n",
    "{\n",
    "  \"type\": \"function\",\n",
    "  \"function\": {\n",
    "    \"name\": \"search\",\n",
    "    \"description\": \"Query the internet for the answer to a question.\",\n",
    "    \"parameters\": {\n",
    "      \"type\": \"object\",\n",
    "      \"properties\": {\n",
    "        \"query\": {\n",
    "          \"type\": \"string\",\n",
    "          \"description\": \"Search term\"\n",
    "        }\n",
    "      },\n",
    "      \"required\": [\"query\"]\n",
    "    }\n",
    "  }\n",
    "}\n",
    "```\n",
    "\n",
    "Note that the tool definition is in the format that OpenAI expects, but not Anthropic. We will come back to this later!\n",
    "\n",
    "[Follow the steps outlined in the documentation](https://arize.com/docs/phoenix/prompt-engineering/how-to-prompts/create-a-prompt) to build your prompt as described above, in the Phoenix UI.\n",
    "\n",
    "Once you have a prompt, you can pull it into the notebook using the Phoenix client."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import * as Prompts from \"npm:@arizeai/phoenix-client/prompts\"\n",
    "\n",
    "const questionSearcherPrompt = await Prompts.getPrompt({ client: px, prompt: { name: \"question-searcher\" } })\n",
    "\n",
    "await Deno.jupyter.md`\n",
    "  ### question-searcher prompt\n",
    "\n",
    "  \\`\\`\\`json\n",
    "  ${JSON.stringify(questionSearcherPrompt, null, 2)}\n",
    "  \\`\\`\\`\n",
    "  `"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Great! Now we have the contents of the `question-searcher` prompt. It's currently in a format that Phoenix understands, so we need to convert it to a format that our SDKs can understand.\n",
    "\n",
    "Luckily, `@arizeai/phoenix-client` comes with a helper function, `toSDK`, to convert the prompt to LLM SDK compatible formats, and inject variables into the prompt at the same time.\n",
    "\n",
    "We will leverage this helper function to convert the prompt to an OpenAI format, and an Anthropic format, and inject the `query` variable into the prompt."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "const openaiPrompt = Prompts.toSDK({ \n",
    "  sdk: \"openai\", \n",
    "  prompt: questionSearcherPrompt, \n",
    "  variables: { query: \"When does the next Arize AI Phoenix release come out?\" } \n",
    "})\n",
    "\n",
    "await Deno.jupyter.md`\n",
    "  ### OpenAI Prompt\n",
    "\n",
    "  \\`\\`\\`json\n",
    "  ${JSON.stringify(openaiPrompt, null, 2)}\n",
    "  \\`\\`\\`\n",
    "`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "const anthropicPrompt = Prompts.toSDK({ \n",
    "  sdk: \"anthropic\", \n",
    "  prompt: questionSearcherPrompt, \n",
    "  variables: { query: \"When does the next Arize Phoenix release come out?\" } \n",
    "})\n",
    "\n",
    "await Deno.jupyter.md`\n",
    "  ### Anthropic Prompt\n",
    "\n",
    "  \\`\\`\\`json\n",
    "  ${JSON.stringify(anthropicPrompt, null, 2)}\n",
    "  \\`\\`\\`\n",
    "`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We now have a set of \"invocation parameters\", fully typed and in the format that OpenAI and Anthropic expect.\n",
    "\n",
    "Notably, this includes tool definitions as well. The format of the tool definitions is different for OpenAI and Anthropic, and the `@arizeai/phoenix-client` library handles this for you via the `toSDK` function.\n",
    "\n",
    "Let's invoke OpenAI with our prompt and then Anthropic with our prompt, and compare the results!\n",
    "\n",
    "First, let's invoke OpenAI with our prompt."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "const response = await openai.chat.completions.create({\n",
    "  ...openaiPrompt,\n",
    "  // you can still override any of the invocation parameters as needed\n",
    "  // for example, you can change the model or stream the response\n",
    "  model: \"gpt-4o-mini\",\n",
    "  stream: false\n",
    "})\n",
    "\n",
    "await Deno.jupyter.md`\n",
    "  ### OpenAI Response\n",
    "\n",
    "  ${response.choices[0].message.content ?? `\\`\\`\\`json\\n${JSON.stringify(response.choices[0].message.tool_calls, null, 2)}\\`\\`\\``}\n",
    "`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Then, let's invoke Anthropic with our prompt."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "const response = await anthropic.messages.create({\n",
    "  ...anthropicPrompt,\n",
    "  // you can still override any of the invocation parameters as needed\n",
    "  // for example, you can change the model or stream the response\n",
    "  model: \"claude-3-5-sonnet-latest\",\n",
    "  stream: false\n",
    "})\n",
    "\n",
    "await Deno.jupyter.md`\n",
    "  ### Anthropic Response\n",
    "\n",
    "  ${response.content.map(c => {\n",
    "    if (c.type === \"text\") {\n",
    "      return c.text\n",
    "    } else if (c.type === \"tool_use\") {\n",
    "      return `\\`\\`\\`json\\n${JSON.stringify(c, null, 2)}\\`\\`\\``\n",
    "    }\n",
    "  }).join(\"\\n\")}\n",
    "`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Great! We've successfully invoked OpenAI, and Anthropic with our prompt and got a response.\n",
    "\n",
    "You can see that this workflow allows you to build a prompt in Phoenix,\n",
    "and then invoke it with multiple LLM providers, all without having to modify the prompt or the invocation parameters yourself.\n",
    "\n",
    "Here are some additional features and capabilities you can explore:\n",
    "\n",
    "- Prompt Versioning / Tagging\n",
    "- Prompt Conversion to other LLM providers"
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "typescript"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
